import express from 'express';
import session from 'express-session';
import { join } from 'path';
import { w7, w7Error, w7Socket } from './w7';
import { ClearCache, ENV, EXPRESS, Headers, Injector, Query, Request, Response, SERVER, USER_STORE, WEBSOCKETSERVER } from './tokens';
import { Module } from './module';
import { createServer } from 'http';
import { container } from 'tsyringe';
import { readFileSync } from "fs-extra";
import { WebSocketServer } from 'ws';
import { startAutoTask } from './task';
import { getSessionConfig } from './getSessionConfig';
import { parseScope } from './parse';
import { md5 } from './md5';
import cors from 'cors';
import { serve, setup } from 'swagger-ui-express'
import { watchAddon } from './watch';
import { logger } from './logger';
import { useAddon } from './addon.util';
import { useEntityManager } from './plugin';

export function createSkerServer(parsedArgs: any): any {
    const env = container.resolve(ENV)
    const app = express()
    container.register(EXPRESS, { useValue: app })
    const server = createServer(app);
    const ws = new WebSocketServer({ noServer: true })
    container.register(SERVER, { useValue: server })
    container.register(WEBSOCKETSERVER, { useValue: ws })
    const root = process.cwd()
    container.register(ClearCache, { useValue: Module.clear.bind(Module) })
    const staticRoot = join(root, 'attachments')
    app.use(express.static(staticRoot, {
        extensions: ['.zip', '.bz2', '.html', '.htm'],
        index: 'index.html'
    }));
    const publicRoot = join(root, 'public')
    app.use(express.static(publicRoot, {
        extensions: ['.js', '.css', '.eot', '.svg', '.ttf', '.woff', '.woff2'],
        index: 'index.html',
    }));
    const addonsRoot = join(root, 'addons')
    app.use((req, res, next) => {
        const path = req.path;
        if (path.includes('public')) {
            express.static(addonsRoot, {
                extensions: ['.js', '.css', '.eot', '.svg', '.ttf', '.woff', '.woff2'],
                index: 'index.html',
            })(req, res, next)
        } else {
            next();
        }
    });
    app.use(express.json())
    // view engine
    const template = require('art-template')
    template.defaults.minimize = true;
    const isDev = !!env.dev;
    app.engine('html', (path, options, callback) => {
        try {
            if (!isDev && container.isRegistered(path)) {
                const render = container.resolve<any>(path);
                const html = render(options);
                callback(null, html)
                return;
            }
            const content = readFileSync(path, 'utf-8')
            const render = template.compile(content)
            container.register(path, { useValue: render })
            const html = render(options);
            callback(null, html)
            return;
        } catch (e) {
            callback(e)
        }
    });
    app.set('view engine', 'html')
    const sessionConfig = getSessionConfig(container)
    container.register(USER_STORE, { useValue: sessionConfig.store })
    app.use(require('cookie-parser')())
    app.use(session(sessionConfig))
    container.register('SESSION', { useValue: sessionConfig })
    // node-schedule 启动内置任务
    startAutoTask()
    app.use('/docs', serve, setup({
        openapi: "3.0.3",
        externalDocs: {
            url: 'http://baidu.com',
            description: '百度'
        },
        info: {
            title: `开发文档`,
            description: '包含接口及其他',
            version: '1.0.0',
            contact: {}
        },
        tags: [
            { name: '@imeepos/core', description: '核心模块' },
            { name: '@fuli/core', description: '福利商城-管理端' },
            { name: '@fuli/company', description: '福利商城-企业端' },
            { name: '@fuli/provider', description: '福利商城-供货端' },
        ],
        servers: [
            { url: 'dev', description: '开发环境', variables: {} },
            { url: 'prod', description: '线上环境', variables: {} }
        ],
        components: {},
    }))
    /**
     * params w7
     * demo /imeepos/core/web/user/login
     */
    app.use(cors())
    app.use('/api', w7)
    app.use(w7)
    /**
     * error
     */
    app.use(w7Error)
    app.get('/', (req, res, next) => {
        const setting = require(join(root, 'data/setting.json'))
        res.redirect(setting.home)
    })
    ws.on('connection', (socket: any, req: any, injector: any) => {
        w7Socket(socket, req, injector)
    })
    server.on('upgrade', async (req, socket, head) => {
        const res = {}
        const childContainer = container.createChildContainer()
        childContainer.register(Injector, { useValue: childContainer })
        childContainer.register(Headers, { useValue: req.headers })
        const url = new URL(req.url, 'http://localhost')
        const query = {}
        url.searchParams.forEach((value, key) => {
            Reflect.set(query, key, value)
        })
        childContainer.register(Query, { useValue: query })
        childContainer.register(Request, { useValue: req })
        childContainer.register(Response, { useValue: res })
        const paths = url.pathname.split('/')
        const [start, ...others] = paths;
        const { scope, addon, platform, controller, action, d } = parseScope(others, req.headers, query);
        const cacheKey = md5(JSON.stringify({ scope, addon, platform, controller, action, d, method: 'ws', query }))
        childContainer.register('REQ_PATH_HASH', { useValue: cacheKey })
        await new Promise<void>((resolve, reject) => {
            require('cookie-parser')()(req, null, (err: any) => {
                if (err) reject(err)
                else resolve()
            })
        })
        await new Promise<void>((resolve, reject) => {
            const sessionConfig = getSessionConfig(container)
            require('express-session')(sessionConfig)(req, res, (err: any) => {
                if (err) reject(err);
                else resolve()
            })
        })
        ws.handleUpgrade(req, socket, head, (client, req) => {
            ws.emit('connection', client, req, childContainer)
        })
    })
    if (isDev && parsedArgs.addon && parsedArgs.scope) {
        watchAddon(parsedArgs.scope, parsedArgs.addon)
    }
    // 启动脚本
    useEntityManager(async m => {
        const list = await m.createQueryBuilder().from('sys_addon', 'addon').select().execute()
        list.map((it: any) => {
            const [scope, addonName] = it.name.split('/')
            useAddon(`get:${scope.replace('@', '')}/${addonName}/web/start`, async addon => {
                const res = await addon({}).catch(e => { });
                console.log(`get:${scope.replace('@', '')}/${addonName}/web/start start`, res)
            }).catch(e => { })
        })
    })
    return { app, server };
}
